package org.zoomdev.zoom.validator;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zoomdev.zoom.aop.Getter;
import org.zoomdev.zoom.aop.Invoker;
import org.zoomdev.zoom.aop.MethodInvokerFactory;
import org.zoomdev.zoom.aop.Setter;
import org.zoomdev.zoom.aop.factory.GetterFactory;
import org.zoomdev.zoom.aop.factory.SetterFactory;
import org.zoomdev.zoom.common.caster.Caster;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.utils.CachedClasses;
import org.zoomdev.zoom.common.utils.Classes;
import org.zoomdev.zoom.ioc.Ioc;
import org.zoomdev.zoom.validator.annotations.*;
import org.zoomdev.zoom.validator.impl.*;

import java.lang.reflect.*;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class Validators {

    private static final ArrayList<ValidatorFactory> FACTORIES = new ArrayList<>(3);
    private static final ConcurrentHashMap<Class, SimpleValidator> CACHE = new ConcurrentHashMap<>();
    private static final DefaultNullValidator DEFAULT_NULL_VALIDATOR = new DefaultNullValidator();
    private static final EmptyValidator EMPTY = new EmptyValidator();
    /**
     * 在严格模式下，null值的提示信息
     */
    public static String nullCheckMessage = "不能为空";
    /**
     * ioc实例
     */
    public static Ioc ioc;
    /**
     * 严格模式 ( true)
     * 默认情况下每一个字段必填，使用Optional控制非必填项
     * <p>
     * 宽松模式 ( false )
     * 默认情况下每一个字段都是可选，使用 @Not系列或者@Require表示必填项,使用Optional控制默认值
     */
    public static boolean restrictMode = false;
    public static MessageFormat messageFormat = (name, message) -> name + ":" + message;

    private Validators() {

    }

    /**
     * 注册一个validator工厂
     *
     * @param factory
     */
    public static void register(ValidatorFactory factory) {
        FACTORIES.add(factory);
    }

    public static void validate(Object obj) {
        if (obj == null) {
            throw new InvalidParameterException("validate对象不能为空");
        }
        Class type = obj.getClass();
        SimpleValidator validator = getValidator(type);
        validator.validate(obj);
    }

    public static SimpleValidator getValidator(Class<?> type) {
        return CACHE.computeIfAbsent(type, k -> {
            List<SimpleValidator> validators = createFieldValidators(k);
            SimpleValidator validator = createMethodValidator(k);
            if (validator != null) {
                validators.add(validator);
            }
            if (validators.isEmpty()) {
                return EMPTY;
            }
            return new ComposedValidator(validators.toArray(new SimpleValidator[validators.size()]));
        });
    }

    /**
     * 创建字段验证器
     */
    public static List<Validator> createValidators(Type type, AnnotatedElement element) {
        List<Validator> validators = new ArrayList<>(3);
        // NotNull验证
        boolean addNotNull = false;
        if (restrictMode) {
            if (!element.isAnnotationPresent(Optional.class)) {
                if (element.isAnnotationPresent(Required.class)) {
                    validators.add(new NullValidator(element.getAnnotation(Required.class).message()));
                } else if (element.isAnnotationPresent(NotEmpty.class)) {
                    validators.add(new NullValidator(element.getAnnotation(NotEmpty.class).message()));
                } else if (element.isAnnotationPresent(NotBlank.class)) {
                    validators.add(new NullValidator(element.getAnnotation(NotBlank.class).message()));
                } else {
                    validators.add(DEFAULT_NULL_VALIDATOR);
                }

                addNotNull = true;
            }
        } else {
            if (!element.isAnnotationPresent(Optional.class)) {
                if (element.isAnnotationPresent(NotNull.class)) {
                    validators.add(new NullValidator(element.getAnnotation(NotNull.class).message()));
                    addNotNull = true;
                }
            }
        }

        if (FACTORIES.size() > 0) {
            for (ValidatorFactory factory : FACTORIES) {
                Validator validator = factory.create(element, ioc);
                if (validator != null) {
                    validators.add(validator);
                }
            }
        }


        if (element.isAnnotationPresent(NotBlank.class)) {
            NotBlank annotation = element.getAnnotation(NotBlank.class);
            if (!addNotNull && !restrictMode && !element.isAnnotationPresent(Optional.class)) {
                validators.add(new NullValidator(annotation.message()));
            }
            validators.add(new NotBlankValidator(annotation));
        }

        if (element.isAnnotationPresent(NotEmpty.class)) {
            NotEmpty annotation = element.getAnnotation(NotEmpty.class);
            if (!addNotNull && !restrictMode && !element.isAnnotationPresent(Optional.class)) {
                validators.add(new NullValidator(annotation.message()));
            }
            validators.add(new NotEmptyValidator(annotation.message()));
        }

        if (element.isAnnotationPresent(Required.class)) {
            Required annotation = element.getAnnotation(Required.class);
            if (!addNotNull && !restrictMode && !element.isAnnotationPresent(Optional.class)) {
                validators.add(new NullValidator(annotation.message()));
            }
            if (type instanceof Class) {
                Class clazz = (Class) type;
                if (clazz.isArray()) {
                    validators.add(new RequiredArrayValidator(annotation.message()));
                } else if (CharSequence.class.isAssignableFrom(clazz)) {
                    validators.add(new NotEmptyValidator(annotation.message()));
                }

            } else if (type instanceof ParameterizedType) {
                Class clazz = (Class) ((ParameterizedType) type).getRawType();
                if (Map.class.isAssignableFrom(clazz)) {
                    validators.add(new RequiredMapValidator(annotation.message()));
                } else if (Collection.class.isAssignableFrom(clazz)) {
                    validators.add(new RequiredCollectionValidator(annotation.message()));
                }
            } else {
                throw new ZoomException("不支持的类型" + type);
            }
        }


        // Length验证
        if (element.isAnnotationPresent(Length.class)) {
            validators.add(new LengthValidator(element.getAnnotation(Length.class)));
        }

        if (element.isAnnotationPresent(Size.class)) {
            validators.add(new LengthValidator(element.getAnnotation(Size.class)));
        }


        // Range验证
        if (element.isAnnotationPresent(Range.class)) {
            validators.add(new RangeValidator(element.getAnnotation(Range.class)));
        }

        if (element.isAnnotationPresent(Max.class)) {
            validators.add(new MaxValidator(element.getAnnotation(Max.class)));
        }


        if (element.isAnnotationPresent(DateFormat.class)) {
            validators.add(new DateFormatValidator(element.getAnnotation(DateFormat.class)));
        }


        // RegexpFormat验证
        if (element.isAnnotationPresent(RegexpFormat.class)) {
            validators.add(new RegexpValidator(element.getAnnotation(RegexpFormat.class)));
        }

        // RegexpFormat验证
        if (element.isAnnotationPresent(Pattern.class)) {
            validators.add(new RegexpValidator(element.getAnnotation(Pattern.class)));
        }

        // CustomValidate验证
        if (element.isAnnotationPresent(CustomValidate.class)) {
            CustomValidate annotation = element.getAnnotation(CustomValidate.class);
            try {
                validators.add(ioc.fetch(annotation.validator()));
            } catch (Exception e) {
                throw new RuntimeException("无法初始化验证器: " + annotation.validator(), e);
            }
        }
        // 组合所有验证器
        return validators.isEmpty() ? null : validators;
    }

    public static Object getDefaultValue(Type type, AnnotatedElement element) {
        if (element.isAnnotationPresent(Optional.class)) {
            Optional optional = element.getAnnotation(Optional.class);
            String defaultValue = optional.defaultValue();
            if (!StringUtils.isEmpty(defaultValue)) {
                try {
                    return Caster.toType(defaultValue, type);
                } catch (Exception e) {
                    throw new ZoomException("默认值转换到类型失败,默认值:" + defaultValue + " 类型:" + type.getTypeName(), e);
                }
            }
        }
        return null;
    }

    public static SimpleValidator createValidator(String name_, Type type, AnnotatedElement element) {
        List<Validator> validators_ = Validators.createValidators(type, element);
        if (validators_ == null || validators_.isEmpty()) {
            return null;
        }

        if (validators_.size() == 1) {
            return new SimpleValidator() {
                private final Validator validator = validators_.get(0);
                private final String name = name_;

                @Override
                public void validate(Object obj) {
                    if (!validator.isValid(obj)) {
                        throw new ValidateException(messageFormat.format(name, validator.getMessage()));
                    }
                }
            };
        } else {
            return new SimpleValidator() {
                private final String name = name_;
                private final Validator[] validators = validators_.toArray(new Validator[validators_.size()]);

                @Override
                public void validate(Object obj) {
                    for (Validator validator : validators) {
                        if (!validator.isValid(obj)) {
                            throw new ValidateException(messageFormat.format(name, validator.getMessage()));
                        }
                    }
                }
            };
        }
    }

    private static final Logger log = LoggerFactory.getLogger(Validators.class);

    private static class InvokerValidator implements SimpleValidator {
        private final Invoker invoker;

        private InvokerValidator(Invoker invoker) {
            this.invoker = invoker;
        }

        @Override
        public void validate(Object obj) {
            try {
                invoker.invoke(obj, Classes.EMPTY);
            } catch (Throwable e) {
                if (e instanceof ValidateException) {
                    throw (ValidateException) e;
                } else {
                    //log.error("发生验证异常", e);
                    throw new ValidateException(e.getMessage());
                }
            }
        }
    }

    private static SimpleValidator createMethodValidator(Class type) {
        for (Method method : CachedClasses.getPublicMethods(type)) {
            if (method.isAnnotationPresent(ValidateFunction.class)) {
                Invoker invoker = MethodInvokerFactory.FACTORY.create(method);
                return new InvokerValidator(invoker);
            }
        }
        return null;
    }

    private static List<SimpleValidator> createFieldValidators(Class type) {
        Field[] fields = CachedClasses.getFields(type);
        List<SimpleValidator> list = new ArrayList<>(fields.length);
        for (Field field : fields) {
            Object defaultValue = getDefaultValue(field.getType(), field);
            List<Validator> validators = createValidators(field.getGenericType(), field);

            if (defaultValue != null) {
                if (validators != null) {
                    if (validators.size() == 1) {
                        list.add(new FieldValidator2(field, validators.get(0), defaultValue));
                    } else {
                        list.add(new FieldValidator1(field, validators.toArray(new Validator[0]), defaultValue));
                    }
                } else {
                    list.add(new FieldWrapper(field, defaultValue));
                }
            } else {
                if (validators != null) {
                    if (validators.size() == 1) {
                        list.add(new FieldValidator0(field, validators.get(0)));
                    } else {
                        list.add(new FieldValidator(field, validators.toArray(new Validator[0])));
                    }
                } else {
                    continue;
                }
            }
        }
        return list;
    }


    /**
     * 格式化输出接口
     */
    @FunctionalInterface
    interface MessageFormat {

        /**
         * @param name    参数名称
         * @param message 报错内容
         * @return
         */
        String format(String name, String message);
    }

    private static final class EmptyValidator implements SimpleValidator {

        @Override
        public void validate(Object obj) {

        }
    }

    private static class ComposedValidator implements SimpleValidator {


        private final SimpleValidator[] validators;


        private ComposedValidator(SimpleValidator[] validators) {
            this.validators = validators;
        }

        @Override
        public void validate(Object obj) {
            for (SimpleValidator validator : validators) {
                validator.validate(obj);
            }
        }
    }

    private final static class DefaultNullValidator implements Validator {


        public DefaultNullValidator() {
        }

        @Override
        public boolean isValid(Object value) {
            return value != null;
        }

        @Override
        public String getMessage() {
            return Validators.nullCheckMessage;
        }
    }

    private final static class FieldWrapper implements SimpleValidator {
        private final Getter getter;
        private final Setter setter;
        private Object defaultValue;

        private FieldWrapper(Field field, Object defaultValue) {
            this.getter = GetterFactory.FACTORY.create(field);
            this.setter = SetterFactory.FACTORY.create(field);
            this.defaultValue = defaultValue;
        }

        @Override
        public void validate(Object obj) {
            Object value = getter.get(obj);
            if (value == null) {
                setter.set(obj, defaultValue);
            }
        }
    }

    private final static class FieldValidator0 implements SimpleValidator {
        private final Getter getter;
        private final String name;
        private final Validator validator;


        public FieldValidator0(Field field, Validator validator) {
            this.getter = GetterFactory.FACTORY.create(field);
            this.name = field.getName();
            this.validator = validator;
        }

        public void validate(Object obj) {
            Object value = getter.get(obj);
            if (!validator.isValid(value)) {
                throw new ValidateException(messageFormat.format(name, validator.getMessage()));
            }

        }

    }

    private final static class FieldValidator2 implements SimpleValidator {
        private final Getter getter;
        private final String name;
        private final Validator validator;

        private final Setter setter;
        private final Object defaultValue;

        public FieldValidator2(Field field, Validator validator, Object defaultValue) {
            this.getter = GetterFactory.FACTORY.create(field);
            this.setter = SetterFactory.FACTORY.create(field);
            this.name = field.getName();
            this.validator = validator;
            this.defaultValue = defaultValue;
        }

        public void validate(Object obj) {
            Object value = getter.get(obj);
            if (value != null) {
                if (!validator.isValid(value)) {
                    throw new ValidateException(messageFormat.format(name, validator.getMessage()));
                }
            } else {
                setter.set(obj, defaultValue);
            }

        }

    }

    private final static class FieldValidator1 implements SimpleValidator {
        private final Getter getter;
        private final String name;
        private final Validator[] validators;

        private final Object defaultValue;

        private final Setter setter;


        public FieldValidator1(Field field, Validator[] validators, Object defaultValue) {
            this.getter = GetterFactory.FACTORY.create(field);
            this.setter = SetterFactory.FACTORY.create(field);
            this.name = field.getName();
            this.validators = validators;
            this.defaultValue = defaultValue;
        }

        public void validate(Object obj) {
            Object value = getter.get(obj);
            if (value != null) {
                for (Validator validator : validators) {
                    if (!validator.isValid(value)) {
                        throw new ValidateException(messageFormat.format(name, validator.getMessage()));
                    }
                }
            } else {
                setter.set(obj, defaultValue);
            }
        }

    }

    private final static class FieldValidator implements SimpleValidator {
        private final Getter getter;
        private final String name;
        private final Validator[] validators;


        public FieldValidator(Field field, Validator[] validators) {
            this.getter = GetterFactory.FACTORY.create(field);
            this.name = field.getName();
            this.validators = validators;
        }

        public void validate(Object obj) {
            Object value = getter.get(obj);
            for (Validator validator : validators) {
                if (!validator.isValid(value)) {
                    throw new ValidateException(messageFormat.format(name, validator.getMessage()));
                }
            }
        }

    }
}
